const { InvalidArgumentError } = require('./error.js');

// @ts-check

class Argument {
    /**
     * Initialize a new command argument with the given name and description.
     * The default is that the argument is required, and you can explicitly
     * indicate this with <> around the name. Put [] around the name for an optional argument.
     *
     * @param {string} name
     * @param {string} [description]
     */

    constructor(name, description) {
        this.description = description || '';
        this.variadic = false;
        this.parseArg = undefined;
        this.defaultValue = undefined;
        this.defaultValueDescription = undefined;
        this.argChoices = undefined;

        switch (name[0]) {
            case '<': // e.g. <required>
                this.required = true;
                this._name = name.slice(1, -1);
                break;
            case '[': // e.g. [optional]
                this.required = false;
                this._name = name.slice(1, -1);
                break;
            default:
                this.required = true;
                this._name = name;
                break;
        }

        if (this._name.length > 3 && this._name.slice(-3) === '...') {
            this.variadic = true;
            this._name = this._name.slice(0, -3);
        }
    }

    /**
     * Return argument name.
     *
     * @return {string}
     */

    name() {
        return this._name;
    }

    /**
     * @api private
     */

    _concatValue(value, previous) {
        if (previous === this.defaultValue || !Array.isArray(previous)) {
            return [value];
        }

        return previous.concat(value);
    }

    /**
     * Set the default value, and optionally supply the description to be displayed in the help.
     *
     * @param {any} value
     * @param {string} [description]
     * @return {Argument}
     */

    default(value, description) {
        this.defaultValue = value;
        this.defaultValueDescription = description;
        return this;
    }

    /**
     * Set the custom handler for processing CLI command arguments into argument values.
     *
     * @param {Function} [fn]
     * @return {Argument}
     */

    argParser(fn) {
        this.parseArg = fn;
        return this;
    }

    /**
     * Only allow argument value to be one of choices.
     *
     * @param {string[]} values
     * @return {Argument}
     */

    choices(values) {
        this.argChoices = values.slice();
        this.parseArg = (arg, previous) => {
            if (!this.argChoices.includes(arg)) {
                throw new InvalidArgumentError(`允许被选择的是： ${this.argChoices.join(', ')}.`);
            }
            if (this.variadic) {
                return this._concatValue(arg, previous);
            }
            return arg;
        };
        return this;
    }

    /**
     * Make argument required.
     */
    argRequired() {
        this.required = true;
        return this;
    }

    /**
     * Make argument optional.
     */
    argOptional() {
        this.required = false;
        return this;
    }
}

/**
 * Takes an argument and returns its human readable equivalent for help usage.
 *
 * @param {Argument} arg
 * @return {string}
 * @api private
 */

function humanReadableArgName(arg) {
    const nameOutput = arg.name() + (arg.variadic === true ? '...' : '');

    return arg.required ? '<' + nameOutput + '>' : '[' + nameOutput + ']';
}

exports.Argument = Argument;
exports.humanReadableArgName = humanReadableArgName;
